﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Cloo;
using Processing.Exceptions;

namespace Processing
{
    /// <summary>
    /// Kapsuliert und behandelt einen Computebuffer prozessorspezifisch.
    /// </summary>
    /// <typeparam name="T">Der Typ.</typeparam>
    public class Buffer<T> : IIBuffer, IBuffer where T : struct
    {
        #region Attribute
        private ComputeBuffer<T> computeBuffer;
        private Processor processor;
        private long length;
        private ComputeCommandQueue commandQueue;
        private ComputeEventList eventList;
        private T[] data;
        private bool readOnly;
        private object dataSync = new object();
        #endregion Attribute

        #region Eigenschaften
        /// <summary>
        /// [INTERN] Gibt den Buffer an.
        /// </summary>
        internal ComputeBuffer<T> ComputeBuffer { get { return computeBuffer; } }

        /// <summary>
        /// [INTERN] Gibt den Prozessor an.
        /// </summary>
        internal Processor Processor { get { return processor; } }

        /// <summary>
        /// Gibt die Größe des Buffers an.
        /// </summary>
        public long Length { get { return length; } }

        /// <summary>
        /// Gibt die Daten des Buffers an.
        /// </summary>
        public T[] Data
        {
            get
            {
                lock (dataSync)
                {
                    if (data == null)
                    {
                        if (commandQueue == null || eventList == null) throw new ProcessorException("Es muss erst eine Kernel ausgeführt werden, bevor die Daten gelesen werden können.");
                        data = new T[length];
                        commandQueue.ReadFromBuffer(computeBuffer, ref data, true, this.eventList);
                        commandQueue = null;
                        eventList = null;
                    }
                    return data;
                }
            }
        }

        /// <summary>
        /// [INTERN] Debugaufruf der Daten.
        /// </summary>
        object IIBuffer.Data { get { return Data; } }
        #endregion Eigenschaften

        #region Methoden
        #region Konstruktoren und Destruktoren
        /// <summary>
        /// Instanziiert und initialisiert den Buffer, wobei die gegebenen Daten übernommen werden.
        /// </summary>
        /// <param name="processor">Der Prozessor.</param>
        /// <param name="data">Die Daten.</param>
        /// <param name="readOnly">Indikator ob es sich um einen ReadOnly Buffer handelt. Wenn ja wird kein Lesen von Daten von der GPU möglich sein, folglich für Debugzwecke geeignet.</param>
        internal Buffer(Processor processor, T[] data, bool readOnly)
        {
            this.readOnly = readOnly;
            if (readOnly) this.data = data;
            this.length = data.LongLength;
            this.computeBuffer = new ComputeBuffer<T>(processor.Context, (readOnly ? ComputeMemoryFlags.ReadOnly : ComputeMemoryFlags.ReadWrite) | ComputeMemoryFlags.CopyHostPointer, data);
            this.processor = processor;
        }

        /// <summary>
        /// Instanziiert und initialisiert den Buffer, wobei die gegebenen Daten übernommen werden.
        /// </summary>
        /// <param name="processor">Der Prozessor.</param>
        /// <param name="data">Die Daten.</param>
        internal Buffer(Processor processor, T[] data) : this(processor, data, false) { }

        /// <summary>
        /// Instanziiert und initialisiert den Buffer, wobei dieser die angegebene Länge hat.
        /// </summary>
        /// <param name="processor">Der Prozessor.</param>
        /// <param name="length">Die Größe des Buffers in Elementen.</param>
        internal Buffer(Processor processor, long length)
        {
            this.length = length;
            this.computeBuffer = new ComputeBuffer<T>(processor.Context, ComputeMemoryFlags.ReadWrite, length);
            this.processor = processor;
        }
        #endregion Konstruktoren und Destruktoren

        /// <summary>
        /// Registriert die Queue zum zurücklesen der Daten zur CPU.
        /// </summary>
        /// <param name="commandQueue">Die Queue.</param>
        /// <param name="eventList">Die Ereignisliste.</param>
        void IIBuffer.RegisterQueue(ComputeCommandQueue commandQueue, ComputeEventList eventList)
        {
            lock (dataSync)
            {
                if (!readOnly)
                {
                    if (commandQueue.Context != processor.Context || eventList[0].Context != processor.Context) throw new KernelException("Die Kernel wurde nicht in der angegebenen Queue ausgeführt.");
                    this.commandQueue = commandQueue;
                    this.eventList = eventList;
                    this.data = null;
                }
            }
        }

        /// <summary>
        /// Gibt das Objekt für den Garbagecollector frei.
        /// </summary>
        public void Dispose()
        {
            lock (dataSync) { } // Falls grade noch ein Thread lesen sollte.
            data = null; // CPU Speicher freigeben
            if (computeBuffer != null)
            {
                computeBuffer.Dispose(); // GPU Speicher freigeben
                computeBuffer = null;
            }
            processor = null;
            commandQueue = null;
            eventList = null;
            dataSync = null;
        }
        #endregion Methoden
    }
}
